/*
 * This file is part of the Marmalade SDK Code Samples.
 *
 * (C) 2001-2012 Marmalade. All Rights Reserved.
 *
 * This source code is intended only as a supplement to the Marmalade SDK.
 *
 * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
 * PARTICULAR PURPOSE.
 */

// Standard includes
#include "DatabaseHandler.h"

// Library includes
#include "IwPropertySet.h"
#include "IwPropertyString.h"
#include "IwUIElement.h"

// External includes
#include "sqlite3.h"

// Macros
IW_MANAGED_IMPLEMENT_FACTORY(CDataBinding);

// Static data
CDatabaseHandler* CDatabaseHandler::s_Singleton = NULL;

//-----------------------------------------------------------------------------
CDatabaseHandler::CDatabaseHandler() : m_DB(NULL)
{
    IwAssertMsg(UI, !s_Singleton,
        ("Creating multiple database handler singletons"));
    s_Singleton = this;
    sqlite3_initialize();
}

CDatabaseHandler::~CDatabaseHandler()
{
    sqlite3_shutdown();
    s_Singleton = NULL;
}

bool CDatabaseHandler::OpenDatabase(const char* pFilename)
{
    IwAssertMsg(UI, !m_DB, ("Already have a database open"));

    if (!m_DB)
    {
        int rc = sqlite3_open(pFilename, &m_DB);

        if (rc == SQLITE_OK)
        {
            sqlite3_update_hook(m_DB, _NotifyUpdate, this);
            return true;
        }
    }

    return false;
}

bool CDatabaseHandler::OpenDatabaseInMemory()
{
    return OpenDatabase(":memory:");
}

bool CDatabaseHandler::CloseDatabase()
{
    IwAssertMsg(UI, m_DB, ("No database to close"));

    if (m_DB)
    {
        // Finalise all prepared statements
        sqlite3_stmt *pStmt;
        while ((pStmt = sqlite3_next_stmt(m_DB, 0)) != NULL )
        {
            sqlite3_finalize(pStmt);
        }

        int rc = sqlite3_close(m_DB);
        m_DB = NULL;

        return rc == SQLITE_OK;
    }

    return false;
}

int CDatabaseHandler::Exec(const char* pSQL)
{
    char* pError = NULL;
    int nRet = sqlite3_exec(m_DB, pSQL, NULL, NULL, &pError);

    if (nRet == SQLITE_OK)
    {
        return sqlite3_changes(m_DB);
    }
    else
    {
        IwAssertMsg(UI, false, ("Failed to execute query %s", pError));
        sqlite3_free(pError);
        return 0;
    }
}

bool CDatabaseHandler::ExecScalar(const char* pSQL, int& outResult)
{
    bool result = false;

    if (sqlite3_stmt* pStatement = PrepareStatement(pSQL))
    {
        if (StepStatement(pStatement) == eRow)
        {
            outResult = sqlite3_column_int(pStatement, 0);
            result = true;
        }

        FinalizeStatement(pStatement);
    }

    return result;
}

bool CDatabaseHandler::TableExists(const char* pTable)
{
    char sqlBuffer[0x100];

    sprintf(sqlBuffer,
        "select count(*) from sqlite_master where type='table' and name='%s'",
        pTable);

    int count = 0;
    if (ExecScalar(sqlBuffer, count))
    {
        return count > 0;
    }
    else
    {
        IwAssertMsg(UI, false, ("Error trying to determine if table %s exists",
            pTable));
        return false;
    }
}

void CDatabaseHandler::AddObserver(IDatabaseObserver* pObserver)
{
    IwAssertMsg(UI, !m_Observers.contains(pObserver),
        ("Registering observer twice"));
    m_Observers.push_back(pObserver);
}

void CDatabaseHandler::RemoveObserver(IDatabaseObserver* pObserver)
{
    IwAssertMsg(UI, m_Observers.contains(pObserver),
        ("Observer not registered"));
    m_Observers.find_and_remove(pObserver);
}

CDatabaseHandler* CDatabaseHandler::Get()
{
    IwAssertMsg(UI, s_Singleton, ("Accessing NULL database handler singleton"));

    return s_Singleton;
}

sqlite3_stmt* CDatabaseHandler::PrepareStatement(const char* pSQL)
{
    sqlite3_stmt* pStatement = NULL;
    int ret = sqlite3_prepare_v2(m_DB, pSQL, -1, &pStatement, NULL);

    IwAssertMsg(UI, ret == SQLITE_OK, ("Error preparing statement"));
    (void) ret;

    return pStatement;
}

CDatabaseHandler::eStepResult CDatabaseHandler::StepStatement(sqlite3_stmt* pStatement)
{
    int ret = sqlite3_step(pStatement);
    switch (ret)
    {
    case SQLITE_ROW:
        return eRow;

    case SQLITE_DONE:
        return eDone;

    default: // Treat everything else as an error
        return eError;
    }
}

bool CDatabaseHandler::FinalizeStatement(sqlite3_stmt* pStatement)
{
    int ret = sqlite3_finalize(pStatement);
    bool success = ret == SQLITE_OK;

    IwAssertMsg(UI, success, ("Error finalizing statement"));

    return success;
}

void CDatabaseHandler::NotifyUpdate(int actionCode, char const* pDB,
    char const* pTbl, int64 rowId)
{
    for (int i=0; i<(int)m_Observers.size(); i++)
    {
        m_Observers[i]->NotifyDatabaseUpdate(actionCode, pDB, pTbl, rowId);
    }
}

void CDatabaseHandler::_NotifyUpdate(void * pData, int actionCode, char const* pDB,
    char const* pTbl, int64 rowId)
{
    CDatabaseHandler* pDBHandler = (CDatabaseHandler*) pData;

    pDBHandler->NotifyUpdate(actionCode, pDB, pTbl, rowId);
}

//-----------------------------------------------------------------------------

CDataBinding::CDataBinding() : m_Property(0), m_RowId(0)
{
    CDatabaseHandler::Get()->AddObserver(this);
}

CDataBinding::~CDataBinding()
{
    CDatabaseHandler::Get()->RemoveObserver(this);
}

bool CDataBinding::ParseAttribute(CIwTextParserITX* pParser,
    const char* pAttrName)
{
    if (!stricmp("property", pAttrName))
    {
        pParser->ReadStringHash(&m_Property);
        return true;
    }
    else if (!stricmp("row_id", pAttrName))
    {
        pParser->ReadInt32(&m_RowId);
        return true;
    }
    else
    {
        return CIwUIElementPropertyBinding::ParseAttribute(pParser, pAttrName);
    }
}

void CDataBinding::DecomposeAttributes(CIwTextDecomposer* pDecomposer) const
{
    pDecomposer->WriteAttibuteNameHash("property", m_Property);
    pDecomposer->WriteAttributeNameVal("row_id", m_RowId);
}

void CDataBinding::Serialise()
{
    CIwUIElementPropertyBinding::Serialise();

    IwSerialiseUInt32(m_Property);
    IwSerialiseInt32(m_RowId);
}

bool CDataBinding::GetProperty(CIwPropertyBase* pProperty)
{
    if (pProperty->GetPropertyDefineID() == m_Property)
    {
        int val = GetValue();

        if (pProperty->isDataType(IwHashString("CIwPropertyString")))
        {
            CIwProperty<CIwPropertyString>* pString =
                IwSafeCast<CIwProperty<CIwPropertyString>* >(pProperty);

            char buffer[0x10];
            sprintf(buffer, "%d", val);
            pString->Set(buffer);

            return true;
        }
        else if (pProperty->isDataType(IwHashString("int16")))
        {
            CIwProperty<int16>* pInt16 =
                IwSafeCast<CIwProperty<int16>* >(pProperty);

            pInt16->Set(val);

            return true;
        }
        else
        {
            IwAssertMsg(UI, false, ("Property %s has unrecognised type %s",
                pProperty->DebugGetPropertyDefine(), pProperty->GetDataTypeString()));

            return false;
        }
    }

    return false;
}

bool CDataBinding::SetProperty(CIwPropertyBase* pProperty)
{
    if (pProperty->GetPropertyDefineID() == m_Property)
    {
        if (pProperty->isDataType(IwHashString("int16")))
        {
            CIwProperty<int16>* pInt16 =
                IwSafeCast<CIwProperty<int16>* >(pProperty);

            int val = pInt16->Get();
            SetValue(val);

            return true;
        }
        else
        {
            IwAssertMsg(UI, false, ("Property %s has unrecognised type %s",
                pProperty->DebugGetPropertyDefine(), pProperty->GetDataTypeString()));

            return false;
        }
    }

    return false;
}

void CDataBinding::Clone(CIwPropertyBinding* pTarget) const
{
    IW_UI_CLONE_SUPERCLASS(pTarget, CDataBinding, CIwUIElementPropertyBinding)

    CDataBinding* pDataBinding = (CDataBinding*) pTarget;

    pDataBinding->m_Property = m_Property;
    pDataBinding->m_RowId = m_RowId;
}

extern CIwArray<class CDataBinding*> g_NotifyBindings;
void CDataBinding::NotifyDatabaseUpdate(int actionCode, char const* pDB,
    char const * pTbl, int64 rowId)
{
    if (rowId == m_RowId)
    {
        g_NotifyBindings.push_back(this);
    }
}

int CDataBinding::GetValue()
{
    char sqlBuffer[0x100];
    sprintf(sqlBuffer, "select val from tbl where id = %d;", m_RowId);

    int val = 0;

    bool success = CDatabaseHandler::Get()->ExecScalar(sqlBuffer, val);
    IwAssertMsg(UI, success, ("Failed to bind (get) property %s", DebugGetName()));
    (void) success;

    return val;
}

void CDataBinding::SetValue(int val)
{
    char sqlBuffer[0x100];
    sprintf(sqlBuffer, "update tbl set val = %d where id = %d;", val, m_RowId);

    bool success = CDatabaseHandler::Get()->Exec(sqlBuffer) == 1;
    IwAssertMsg(UI, success, ("Failed to bind (set) property %s", DebugGetName()));
    (void) success;
}
